import yaml
import os
import struct
from ryu.base import app_manager
from ryu.controller import ofp_event, dpset
from ryu.controller.handler import MAIN_DISPATCHER, CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib import addrconv
import networkx as nx
from Topo import Topo

def ipv4_text_to_int(ip_text):
    if ip_text == 0:
        return ip_text
    assert isinstance(ip_text, str)
    return struct.unpack('!I', addrconv.ipv4.text_to_bin(ip_text))[0]

class App(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(App, self).__init__(*args, **kwargs)
        self.dps = []
        self.conffile = os.environ['CONF']


    def set_flow(self, cookie, priority, dl_type=0, dl_dst=0, dl_vlan=0,
                 nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
                 nw_proto=0, idle_timeout=0, actions=None):
        ofp = self.dp.ofproto
        ofp_parser = self.dp.ofproto_parser
        cmd = ofp.OFPFC_ADD

        # Match
        match = ofp_parser.OFPMatch()
        if dl_type:
            match.set_dl_type(dl_type)
        if dl_dst:
            match.set_dl_dst(dl_dst)
        if dl_vlan:
            match.set_vlan_vid(dl_vlan)
        if nw_src:
            match.set_ipv4_src_masked(ipv4_text_to_int(nw_src),
                                      mask_ntob(src_mask))
        if nw_dst:
            match.set_ipv4_dst_masked(ipv4_text_to_int(nw_dst),
                                      mask_ntob(dst_mask))
        if nw_proto:
            if dl_type == ether.ETH_TYPE_IP:
                match.set_ip_proto(nw_proto)
            elif dl_type == ether.ETH_TYPE_ARP:
                match.set_arp_opcode(nw_proto)

        # Instructions
        actions = actions or []
        inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
                                                 actions)]

        m = ofp_parser.OFPFlowMod(self.dp, cookie, 0, 0, cmd, idle_timeout,
                                  0, priority, UINT32_MAX, ofp.OFPP_ANY,
                                  ofp.OFPG_ANY, 0, match, inst)
        self.dp.send_msg(m)

    def set_routing_flow(self, cookie, priority, outport, dl_vlan=0,
                         nw_src=0, src_mask=32, nw_dst=0, dst_mask=32,
                         src_mac=0, dst_mac=0, idle_timeout=0, dec_ttl=False):
        ofp = self.dp.ofproto
        ofp_parser = self.dp.ofproto_parser

        dl_type = ether.ETH_TYPE_IP

        actions = []
        if dec_ttl:
            actions.append(ofp_parser.OFPActionDecNwTtl())
        if src_mac:
            actions.append(ofp_parser.OFPActionSetField(eth_src=src_mac))
        if dst_mac:
            actions.append(ofp_parser.OFPActionSetField(eth_dst=dst_mac))
        if outport is not None:
            actions.append(ofp_parser.OFPActionOutput(outport, 0))

        self.set_flow(cookie, priority, dl_type=dl_type, dl_vlan=dl_vlan,
                      nw_src=nw_src, src_mask=src_mask,
                      nw_dst=nw_dst, dst_mask=dst_mask,
                      idle_timeout=idle_timeout, actions=actions)

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        self.dps.append(datapath)
        # if datapath.id == 1:
        #     print(datapath.id)
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.3')
        #     actions = [parser.OFPActionOutput(2)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.3')
        #     actions = [parser.OFPActionOutput(2)]
        #     # self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.1')
        #     actions = [parser.OFPActionOutput(1)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.1')
        #     actions = [parser.OFPActionOutput(1)]
        #     # self.add_flow(datapath, 1, match, actions)
        # elif datapath.id == 2:
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.3')
        #     actions = [parser.OFPActionOutput(2)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.3')
        #     actions = [parser.OFPActionOutput(2)]
        #     # self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.2')
        #     actions = [parser.OFPActionOutput(1)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.2')
        #     actions = [parser.OFPActionOutput(1)]
        #     # self.add_flow(datapath, 1, match, actions)
        # elif datapath.id == 3:
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.3')
        #     actions = [parser.OFPActionOutput(1)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.3')
        #     actions = [parser.OFPActionOutput(1)]
        #     # self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.1')
        #     actions = [parser.OFPActionOutput(2)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.1')
        #     actions = [parser.OFPActionOutput(2)]
        #     # self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.2')
        #     actions = [parser.OFPActionOutput(3)]
        #     self.add_flow(datapath, 1, match, actions)
        #     match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.2')
        #     actions = [parser.OFPActionOutput(3)]
        #     # self.add_flow(datapath, 1, match, actions)

        
        if len(self.dps) == 3:
            self.program()

    def program(self):
        with open(self.conffile) as f:
            txt = f.read()
            data = yaml.load(txt)
        print(data['static_resources']['clusters'])
        topo = Topo('topo.json')

        for endpoint in data['static_resources']['clusters'][0]['load_assignment']['endpoints'][0]['lb_endpoints']:
            host_ip = endpoint['endpoint']['address']['socket_address']['address']
            host_port = int(endpoint['endpoint']['address']['socket_address']['port_value'])
            path = topo.shortest_path('pxy', topo.ipv4_to_node[host_ip])
            print('path:')
            print(path)
            for i in range(len(path) - 1):
                src = path[i]
                dst = path[i+1]
                if src[0] == 's': # only program switches
                    datapath = self.get_dp_by_id(int(src[-1:]))
                    parser = datapath.ofproto_parser
                    match = parser.OFPMatch(eth_type=0x0800, ipv4_dst=host_ip, ip_proto=6, tcp_dst=host_port)
                    actions = [parser.OFPActionOutput(topo.G.edges[src, dst]['link'][src])]
                    self.add_flow(datapath, 1, match, actions)
                    match = parser.OFPMatch(eth_type=0x0806, arp_tpa=host_ip)
                    actions = [parser.OFPActionOutput(topo.G.edges[src, dst]['link'][src])]
                    self.add_flow(datapath, 1, match, actions)

                # now program the reverse direction
                src = path[len(path) - i -1]
                dst = path[len(path) - i - 2]
                if src[0] == 's': # only program switches
                    datapath = self.get_dp_by_id(int(src[-1:]))
                    parser = datapath.ofproto_parser
                    match = parser.OFPMatch(eth_type=0x0800, ipv4_dst='10.0.0.3', ip_proto=6, tcp_src=host_port)
                    actions = [parser.OFPActionOutput(topo.G.edges[src, dst]['link'][src])]
                    self.add_flow(datapath, 1, match, actions)
                    match = parser.OFPMatch(eth_type=0x0806, arp_tpa='10.0.0.3')
                    actions = [parser.OFPActionOutput(topo.G.edges[src, dst]['link'][src])]
                    self.add_flow(datapath, 1, match, actions)
        
        print('pgend')

    def get_dp_by_id(self, id):
        for dp in self.dps:
            if id == dp.id:
                return dp
        return None
    
    def add_flow(self, datapath, priority, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # construct flow_mod message and send it.
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
                                             actions)]
        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                match=match, instructions=inst)
        datapath.send_msg(mod)

    # @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    # def packet_in_handler(self, ev):
    #     msg = ev.msg
    #     dp = msg.datapath
    #     ofp = dp.ofproto
    #     ofp_parser = dp.ofproto_parser

    #     actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]

    #     data = None
    #     if msg.buffer_id == ofp.OFP_NO_BUFFER:
    #          data = msg.data

    #     out = ofp_parser.OFPPacketOut(
    #         datapath=dp, buffer_id=msg.buffer_id, in_port=msg.match['in_port'],
    #         actions=actions, data = data)
    #     dp.send_msg(out)


